
/************************************************************************/
/* 
*/
/************************************************************************/

#define  _FILE_OFFSET_BITS 64
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#define EGL_EGLEXT_PROTOTYPES
#define GL_GLEXT_PROTOTYPES

#include <gbm.h>
#include <drm.h>
#include <xf86drm.h>
#include <xf86drmMode.h>

#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include <libevdev/libevdev.h>

#include "light.h"
#include "linux/vt.h"


int							g_hDevice;
uint32_t					g_hCrtcId;
uint32_t					g_hConnectorId;
drmModeModeInfo g_stDisplayMode;


static void initKms( )
{
	static const char device_name[] = "/dev/dri/card0";
	g_hDevice = open(device_name, O_RDWR);
	if (g_hDevice < 0) 
	{
		/* Probably permissions error */
		fprintf(stderr, "couldn't open %s, skipping\n", device_name);
		exit(0);
		return;
	}

	drmModeRes *resources;
	resources = drmModeGetResources(g_hDevice);
	drmModeConnector *connector = NULL;
	for (int i = 0; i < resources->count_connectors; i++)
	{
		connector = drmModeGetConnector(g_hDevice, resources->connectors[i]);
		if (connector->connection == DRM_MODE_CONNECTED && connector->count_modes > 0)
		{
			drmModeEncoder *encoder;
			encoder = drmModeGetEncoder( g_hDevice, connector->encoders[connector->encoder_id] );
			for ( unsigned j = 0; j < resources->count_crtcs; ++j )
			{   
				if ( encoder->possible_crtcs & (1 << j) )
				{
					g_hCrtcId = resources->crtcs[j];
					g_hConnectorId = connector->connector_id;
					g_stDisplayMode = connector->modes[0];
					drmModeFreeConnector(connector);
					return;
				}
			}
		}

		drmModeFreeConnector(connector);
	}
}

struct DoubleBuffer
{
	DoubleBuffer() { ptrBufferObject = NULL; hKmsFb = 0; }

	gbm_bo	*ptrBufferObject;
	uint32_t	hKmsFb;
};

void swapBuffers( gbm_surface *gs  )
{
	static DoubleBuffer buffer[2];
	static int current = 0;

	buffer[current].ptrBufferObject = gbm_surface_lock_front_buffer(gs);

	uint32_t hKmsBuffer = gbm_bo_get_handle(buffer[current].ptrBufferObject).u32;
	uint32_t stride = gbm_bo_get_stride(buffer[current].ptrBufferObject);
	int ret;
	
	ret = drmModeAddFB(g_hDevice, g_stDisplayMode.hdisplay, g_stDisplayMode.vdisplay, 24, 32, stride, hKmsBuffer, &buffer[current].hKmsFb);
	ret = drmModeSetCrtc(g_hDevice, g_hCrtcId, buffer[current].hKmsFb, 0, 0, &g_hConnectorId, 1, &g_stDisplayMode);
	ret = drmModePageFlip(g_hDevice, g_hCrtcId, buffer[current].hKmsFb, DRM_MODE_PAGE_FLIP_EVENT, NULL);

	/* Clear */
	current = 1-current;
	if ( buffer[current].ptrBufferObject !=NULL &&  buffer[current].hKmsFb !=0 )
	{
		drmModeRmFB(g_hDevice, buffer[current].hKmsFb);
		gbm_surface_release_buffer(gs, buffer[current].ptrBufferObject);
	}
}

void modeset_page_flip_event(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data)
{
}

void framesync()
{
	static long eclipsed = 0; // nsec
	static long T = 1000000000.0/60.0;

	timeval tv;
	timespec req, rem;
	gettimeofday(&tv, NULL);
	eclipsed += tv.tv_usec*1000.0;

	if (eclipsed>T)
	{
		eclipsed = 0.0;
	}

	req.tv_sec = 0;
	req.tv_nsec = T-eclipsed;
	nanosleep( &req, &rem );
}

template<typename T>
void mainloop( T& painter, GLWidget* w = NULL )
{
	struct libevdev *dev = NULL;
	int fd;
	int rc = 1;
	fd = open("/dev/input/event0", O_RDONLY|O_NONBLOCK);
	rc = libevdev_new_from_fd(fd, &dev);
	if (rc < 0) 
	{        
		fprintf(stderr, "Failed to init libevdev (%s)\n", strerror(-rc));
		exit(1);
	}


	fd_set fds;
	struct timeval v;
	drmEventContext ev;

	FD_ZERO(&fds);
	memset(&v, 0, sizeof(v));
	memset(&ev, 0, sizeof(ev));
	ev.version = DRM_EVENT_CONTEXT_VERSION;
	ev.page_flip_handler = modeset_page_flip_event;

	struct input_event evt;

	while (1) 
	{
		rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &evt);
		if (rc == 0) 
		{
			if( evt.type == EV_KEY && evt.code== KEY_ESC )
			{
				break;
			}
			if ( w && evt.type == EV_KEY )
			{
				w->keyEvent(evt.code, evt.value);
			}
		}

		FD_SET(g_hDevice, &fds);

		int ret;
		ret = select( g_hDevice+1, &fds, NULL, NULL, &v);
		if (ret < 0) 
		{
			fprintf(stderr, "select() failed with %d: %m\n", errno);
			break;
		} 
		else if (FD_ISSET(g_hDevice, &fds))
		{
			drmHandleEvent(g_hDevice, &ev);
			framesync();
			painter();
		}
	}
}

void switchVt( int t )
{
	int fd = open("/dev/tty1", O_RDONLY);
	ioctl(fd, VT_ACTIVATE, t);
}

int main(int argc, char *argv[])
{ 
	switchVt(8);
	initKms();

	int width = g_stDisplayMode.hdisplay, height = g_stDisplayMode.vdisplay;

	struct gbm_device *gbm;
	gbm = gbm_create_device(g_hDevice);

	struct gbm_surface *gs;
	gs = gbm_surface_create(gbm, width, height,  GBM_BO_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING );

	Light *demoLight = new Light( gbm, gs );
	demoLight->initgl();

	demoLight->draw(width, height);
	swapBuffers( gs );

	auto drawfunc = [ & ] ( )->void { 
		demoLight->draw(width, height); swapBuffers( gs ); 
	};
	
	mainloop(drawfunc, demoLight);

	delete demoLight;
	gbm_surface_destroy(gs);
	gbm_device_destroy(gbm);
	close(g_hDevice);
	switchVt(1);
	return 0;
}
